/* *******************************************************************
 * Copyright (c) 2004 Contributors.
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * Contributors:
 *    Andy Clement     Initial version
 *    Helen Hawkins    Converted to new interface (bug 148190)
 * ******************************************************************/
package org.aspectj.ajde.core.tests;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import org.aspectj.ajde.core.AjdeCoreTestCase;
import org.aspectj.ajde.core.TestCompilerConfiguration;
import org.aspectj.ajde.core.TestMessageHandler;
import org.aspectj.bridge.IMessage;

public class ReweavableTest extends AjdeCoreTestCase {

	private static final boolean debugTests = false;

	public static final String binDir = "bin";

	public static final String indir1Name = "indir1";
	public static final String indir2Name = "indir2";
	public static final String injarName = "injar.jar";
	public static final String outjarName = "/bin/output.jar";

	private TestMessageHandler handler;
	private TestCompilerConfiguration compilerConfig;

	@Override
	protected void setUp() throws Exception {
		super.setUp();
		initialiseProject("ReweavableTest");
		handler = (TestMessageHandler) getCompiler().getMessageHandler();
		handler.dontIgnore(IMessage.INFO);
		compilerConfig = (TestCompilerConfiguration) getCompiler().getCompilerConfiguration();
	}

	@Override
	protected void tearDown() throws Exception {
		super.tearDown();
		handler = null;
		compilerConfig = null;
	}

	/**
	 * Aim: Check we haven't damaged 'normal compilation' when not supplying -Xreweavable. Also determines baseline sizes for the
	 * compiled class files for later comparison.
	 *
	 * Inputs to the compiler: NonReweavable1.lst -> CalculatePI.java -> Logger.aj -> -verbose -> -noExit
	 *
	 * Expected result = Compile successful, the types will not be reweavable and the weaver should not report it is running in
	 * reweavable mode.
	 */
	public void testNonReweavableCompile() {
		if (debugTests)
			System.out.println("testNonReweavableCompile: Building with NonReweavable1.lst");
		String[] files = new String[] { "CalculatePI.java", "Logger.aj" };
		compilerConfig.setProjectSourceFiles(getSourceFileList(files));
		compilerConfig.setNonStandardOptions("-verbose -noExit -XnotReweavable");

		doBuild(true);

		assertFalse("Did not expect to find a message about the weaver operating " + "in reweavable mode",
				checkFor("weaver operating in reweavable mode"));

		File fCalc = openFile("bin/CalculatePI.class");
		File fLog = openFile("bin/Logger.class");
		assertTrue("bin/CalculatePI.class should exist?!?", fCalc.exists());
		assertTrue("bin/Logger.class should exist?!?", fLog.exists());
		if (debugTests)
			System.out.println("CalculatePI.class is of size: " + fCalc.length());
		if (debugTests)
			System.out.println("Logger.class is of size: " + fLog.length());
		if (debugTests)
			System.out.println("\n\n\n");
		/* nonreweavesize_CalculatePI = (int) */fCalc.length();
		/* nonreweavesize_Logger = (int) */fLog.length();
	}

	/**
	 * Aim: Basic call to -Xreweavable. Weaver should report it is in reweavable mode and the classes produced should be much larger
	 * than normal classes (those produced in the first test).
	 *
	 * Inputs to the compiler: Reweavable1.lst -> CalculatePI.java -> Logger.aj -> -Xreweavable -> -verbose -> -noExit
	 *
	 * Expected result = Compile successful, the types will be reweavable and the weaver should report it is running in reweavable
	 * mode. The files produced should be larger than those created during the last test.
	 */
	public void testReweavableCompile() {
		if (debugTests)
			System.out.println("testReweavableCompile: Building with Reweavable1.lst");
		String[] files = new String[] { "CalculatePI.java", "Logger.aj" };
		compilerConfig.setProjectSourceFiles(getSourceFileList(files));
		compilerConfig.setNonStandardOptions("-verbose -noExit");

		doBuild(true);

		assertTrue("Expected a message about operating in reweavable mode, but " + "didn't get one",
				checkFor("weaver operating in reweavable mode"));

		File fCalc = openFile("bin/CalculatePI.class");
		File fLog = openFile("bin/Logger.class");
		assertTrue("bin/CalculatePI.class should exist?!?", fCalc.exists());
		assertTrue("bin/Logger.class should exist?!?", fLog.exists());
		if (debugTests)
			System.out.println("CalculatePI.class is of size: " + fCalc.length());
		if (debugTests)
			System.out.println("Logger.class is of size: " + fLog.length());
		// Temporarily remove these tests - it seems the order in which the
		// testXXX methods are run cannot be relied upon
		// so reweavablesize_XXX fields might not have been set yet.
		// assertTrue("Reweavable version should be larger than non-reweavable
		// version of CalculatePI",
		// fCalc.length()>nonreweavesize_CalculatePI);
		// assertTrue("Reweavable version should be larger than non-reweavable
		// version of Logger",
		// fLog.length()>nonreweavesize_Logger);

		/* reweavablesize_CalculatePI = (int) */fCalc.length();
		/* reweavablesize_Logger = (int) */fLog.length();

		if (debugTests)
			System.out.println("\n\n\n");
	}

	/**
	 * Aim: Use the optional ':compress' modifier on -Xreweavable. This causes some of the meta-data for use in reweaving to be
	 * compressed. It should succeed and produce class files smaller than straight -Xreweavable but larger than without specifying
	 * -Xreweavable.
	 *
	 * Inputs to the compiler: ReweavableCompress1.lst -> CalculatePI.java -> Logger.aj -> -Xreweavable:compress -> -verbose ->
	 * -noExit
	 *
	 * Expected result = Compile successful, the types will be reweavable and the weaver should report it is running in reweavable
	 * mode. The files created should have a size between the non-reweavable versions and the reweavable (without compression)
	 * versions.
	 */
	public void testReweavableCompressCompile() {
		if (debugTests)
			System.out.println("testReweavableCompressCompile: Building with ReweavableCompress1.lst");

		String[] files = new String[] { "CalculatePI.java", "Logger.aj" };
		compilerConfig.setProjectSourceFiles(getSourceFileList(files));
		compilerConfig.setNonStandardOptions("-Xreweavable:compress -verbose -noExit");

		doBuild(true);

		assertTrue("Expected a message about operating in reweavable mode, but didn't get one",
				checkFor("weaver operating in reweavable mode"));

		File fCalc = openFile("bin/CalculatePI.class");
		File fLog = openFile("bin/Logger.class");
		assertTrue("bin/CalculatePI.class should exist?!?", fCalc.exists());
		assertTrue("bin/Logger.class should exist?!?", fLog.exists());
		int calclen = (int) fCalc.length();
		int loglen = (int) fLog.length();
		if (debugTests)
			System.out.println("CalculatePI.class is of size: " + calclen);
		if (debugTests)
			System.out.println("Logger.class is of size: " + loglen);
		// Temporarily remove these tests - it seems the order in which the
		// testXXX methods are run cannot be relied upon
		// so reweavablesize_XXX fields might not have been set yet.
		// assertTrue("Reweavable version should be larger than non-reweavable
		// version of CalculatePI",
		// calclen>nonreweavesize_CalculatePI);
		// assertTrue("Reweavable version should be larger than non-reweavable
		// version of Logger",
		// loglen>nonreweavesize_Logger);

		// Temporarily remove these tests - it seems the order in which the
		// testXXX methods are run cannot be relied upon
		// so reweavablesize_XXX fields might not have been set yet.
		// assertTrue("Reweavable (with compression) version should be smaller
		// than reweavable (without compression) version of CalculatePI:" +
		// " Compressed version:"+calclen+"bytes Non-compressed
		// version:"+reweavablesize_CalculatePI+"bytes",
		// calclen<reweavablesize_CalculatePI);
		// assertTrue("Reweavable (with compression) version should be smaller
		// than reweavable (without compression) version of Logger"+
		// " Compressed version:"+loglen+"bytes Non-compressed
		// version:"+reweavablesize_Logger+"bytes",
		// loglen<reweavablesize_Logger);

		if (debugTests)
			System.out.println("\n\n\n");
	}

	/**
	 * Aim: The tests above have determined that reweaving appears to be behaving in terms of the .class files it is creating. Now
	 * lets actually attempt a reweave. For this, we build two files as reweavable and then build a single file whilst specifying an
	 * inpath that contains the .class files from the first compile. This should succeed.
	 *
	 * Inputs to the first compile: Reweavable1.lst -> CalculatePI.java -> Logger.aj -> -Xreweavable -> -verbose -> -noExit
	 *
	 * Input to the second compile: Reweavable2.lst -> SecondAspect.aj -> -Xreweavable -> -verbose -> -noExit -inpath bin\.
	 *
	 * Expected result = Both compiles will succeed.
	 */
	public void testReweavableSimpleCompile() {
		if (debugTests)
			System.out.println("testReweavableSimpleCompile: Building with Reweavable1.lst");

		String[] files = new String[] { "CalculatePI.java", "Logger.aj" };
		compilerConfig.setProjectSourceFiles(getSourceFileList(files));
		compilerConfig.setNonStandardOptions("-verbose -noExit");

		doBuild(true);

		assertTrue("Expected a message about operating in reweavable mode, but didn't get one",
				checkFor("weaver operating in reweavable mode"));

		if (debugTests)
			System.out.println("\ntestReweavableSimpleCompile: Building with Reweavable2.lst");
		Set<File> paths = new HashSet<>();
		paths.add(openFile(binDir));
		compilerConfig.setInpath(paths);
		String[] newFiles = new String[] { "SecondAspect.aj" };
		compilerConfig.setProjectSourceFiles(getSourceFileList(newFiles));

		doBuild(true);

		String expMessage = "successfully verified type Logger exists";
		assertTrue("Expected message '" + expMessage + "' but did not find it", checkFor(expMessage));

		File fCalc = openFile("bin/CalculatePI.class");
		File fLog = openFile("bin/Logger.class");
		File fSec = openFile("bin/SecondAspect.class");
		assertTrue("bin/CalculatePI.class should exist?!?", fCalc.exists());
		assertTrue("bin/Logger.class should exist?!?", fLog.exists());
		assertTrue("bin/SecondAspect.class should exist?!?", fSec.exists());

		if (debugTests)
			System.out.println("\n\n\n");
	}

	/**
	 * Aim: Based on the test above, if we delete Logger.class between the first and second compiles the second compile should fail
	 * because there is not enough information to reweave CalculatePI
	 *
	 * Inputs to the first compile: Reweavable1.lst -> CalculatePI.java -> Logger.aj -> -Xreweavable -> -verbose -> -noExit
	 *
	 * Input to the second compile: Reweavable2.lst -> SecondAspect.aj -> -Xreweavable -> -verbose -> -noExit -inpath bin\.
	 *
	 * Expected result = Second compile will fail - reporting that Logger is missing (it 'touched' in the first compile CalculatePI)
	 */
	public void testForReweavableSimpleErrorCompile() {
		if (debugTests)
			System.out.println("testForReweavableSimpleErrorCompile: Building with Reweavable2.lst");

		String[] files = new String[] { "CalculatePI.java", "Logger.aj" };
		compilerConfig.setProjectSourceFiles(getSourceFileList(files));
		compilerConfig.setNonStandardOptions("-verbose -noExit");

		doBuild(true);

		assertTrue("Expected a message about operating in reweavable mode, but didn't get one",
				checkFor("weaver operating in reweavable mode"));

		assertTrue("Could not delete bin/Logger.class??", openFile("bin/Logger.class").delete());

		if (debugTests)
			System.out.println("\ntestForReweavableSimpleErrorCompile: Building with Reweavable2.lst");
		Set<File> paths = new HashSet<>();
		paths.add(openFile(binDir));
		compilerConfig.setInpath(paths);
		String[] newFiles = new String[] { "SecondAspect.aj" };
		compilerConfig.setProjectSourceFiles(getSourceFileList(newFiles));

		doBuild(true);

		String expMessage = "aspect Logger cannot be found when reweaving CalculatePI";
		assertTrue("Expected message '" + expMessage + "' but did not find it", checkFor(expMessage));

		File fCalc = openFile("bin/CalculatePI.class");
		File fLog = openFile("bin/Logger.class");
		File fSec = openFile("bin/SecondAspect.class");
		assertTrue("bin/CalculatePI.class should exist!", fCalc.exists());
		assertTrue("bin/Logger.class should not exist!", !fLog.exists());
		assertTrue("bin/SecondAspect.class should not exist!", fSec.exists());

		if (debugTests)
			System.out.println("\n\n\n");
	}

	/**
	 * Aim: Based on the test above, if we delete Logger.class between the first and second compiles the second compile should fail
	 * because there is not enough information to reweave CalculatePI
	 *
	 * Inputs to the first compile: TJP1.lst -> tjp/Demo.java -> tjp/GetInfo.java -> -Xreweavable -> -verbose -> -noExit
	 *
	 * Now, delete bin\tjp\GetInfo.class and do a compile with: TJP2.lst -> -Xreweavable -> -verbose -> -noExit -inpath bin\.
	 *
	 * Expected result = Second compile will fail - reporting that tjp.GetInfo is missing (it 'touched' in the first compile
	 * tjp.Demo)
	 */
	public void testErrorScenario2Compile() {
		if (debugTests)
			System.out.println("testErrorScenario2: Building with TJP1.lst");

		String[] files = new String[] { "tjp" + File.separator + "Demo.java", "tjp" + File.separator + "GetInfo.java" };
		compilerConfig.setProjectSourceFiles(getSourceFileList(files));
		compilerConfig.setNonStandardOptions("-verbose -noExit");

		doBuild(true);

		assertTrue("Expected a message about operating in reweavable mode, but didn't get one",
				checkFor("weaver operating in reweavable mode"));

		assertTrue("Could not delete bin/tjp/GetInfo.class??", openFile("bin/tjp/GetInfo.class").delete());

		if (debugTests)
			System.out.println("\ntestErrorScenario2: Building with TJP2.lst");
		Set<File> paths = new HashSet<>();
		paths.add(openFile(binDir));
		compilerConfig.setInpath(paths);
		compilerConfig.setProjectSourceFiles(new ArrayList<>());
		doBuild(true);

		String expMessage = "aspect tjp.GetInfo cannot be found when reweaving tjp.Demo";
		assertTrue("Expected message '" + expMessage + "' but did not find it", checkFor(expMessage));

		File fDemo = openFile("bin/tjp/Demo.class");
		File fGetInfo = openFile("bin/tjp/GetInfo.class");
		assertTrue("bin/tjp/Demo.class should exist!", fDemo.exists());
		assertTrue("bin/tjp/GetInfo.class should not exist!", !fGetInfo.exists());

		if (debugTests)
			System.out.println("\n\n\n");
	}

	public void testWorkingScenario2Compile() {
		if (debugTests)
			System.out.println("testWorkingScenario2: Building with TJP1.lst");
		String[] files = new String[] { "tjp" + File.separator + "Demo.java", "tjp" + File.separator + "GetInfo.java" };
		compilerConfig.setProjectSourceFiles(getSourceFileList(files));
		compilerConfig.setNonStandardOptions("-Xreweavable:compress -verbose -noExit");

		doBuild(true);

		assertTrue("Expected a message about operating in reweavable mode, but didn't get one",
				checkFor("weaver operating in reweavable mode"));

		if (debugTests)
			System.out.println("\ntestWorkingScenario2: Building with TJP2.lst");
		Set<File> paths = new HashSet<>();
		paths.add(openFile(binDir));
		compilerConfig.setInpath(paths);
		compilerConfig.setProjectSourceFiles(new ArrayList<>());
		doBuild(true);

		String expMessage = "successfully verified type tjp.GetInfo exists";
		assertTrue("Expected message '" + expMessage + "' but did not find it", checkFor(expMessage));

		File fGetInfo = openFile("bin/tjp/GetInfo.class");
		File fDemo = openFile("bin/tjp/Demo.class");
		assertTrue("bin/tjp/GetInfo.class should exist!", fGetInfo.exists());
		assertTrue("bin/tjp/Demo.class should not exist!", fDemo.exists());

		if (debugTests)
			System.out.println("\n\n\n");
	}
}
